﻿Option Strict On
Option Explicit On

Imports System.Runtime.InteropServices
Imports System.Collections.Generic

Public Class OpenGLViewController

    ' API constants

    Private Const CLASS_NAME As String = "GL_VIEW"
    Private Const APP_TITLE As String = "OpenGL view window"
    Private Const WS_EX_APPWINDOW As Integer = &H40000
    Private Const WS_POPUP As Integer = &H80000000
    Private Const GL_VERSION As Integer = &H1F02
    Private Const CS_HREDRAW As Integer = &H2
    Private Const CS_VREDRAW As Integer = &H1
    Private Const PM_REMOVE As Integer = &H1
    Private Const SW_SHOWNORMAL As Integer = 1
    Private Const WM_DESTROY As Integer = &H2
    Private Const WM_LBUTTONDOWN As Integer = &H201
    Private Const WM_RBUTTONDOWN As Integer = &H204
    Private Const WM_KEYDOWN As Integer = &H100
    Private Const WM_KEYUP As Integer = &H101
    Private Const PFD_DRAW_TO_WINDOW As Integer = 4
    Private Const PFD_SUPPORT_OPENGL As Integer = 32
    Private Const PFD_DOUBLEBUFFER As Integer = 1
    Private Const PFD_TYPE_RGBA As Integer = 0
    Private Const PFD_MAIN_PLANE As Integer = 0
    Private Const GL_MATRIX_MODE As Integer = &HBA0
    Private Const GL_MODELVIEW As Integer = &H1700
    Private Const GL_PROJECTION As Integer = &H1701
    Private Const GL_TEXTURE As Integer = &H1702
    Private Const GL_LEQUAL As Integer = &H203
    Private Const GL_DEPTH_TEST As Integer = &HB71
    Private Const GL_PERSPECTIVE_CORRECTION_HINT As Integer = &HC50
    Private Const GL_NICEST As Integer = &H1102
    Private Const GL_SMOOTH As Integer = &H1D01
    Private Const GL_TRIANGLES As Integer = 4
    Private Const GL_COLOR_BUFFER_BIT As Integer = &H4000
    Private Const GL_DEPTH_BUFFER_BIT As Integer = &H100
    Private Const GL_UNSIGNED_BYTE As Integer = &H1401
    Private Const GL_RGB8 As Integer = &H8051
    Private Const GL_TEXTURE_2D As Integer = &HDE1
    Private Const GL_TEXTURE_MIN_FILTER As Integer = &H2801
    Private Const GL_TEXTURE_MAG_FILTER As Integer = &H2800
    Private Const GL_QUADS As Integer = &H7
    Private Const GL_BGR_EXT As Integer = 32992
    Private Const GL_LINEAR As Integer = 9729
    Private Const GL_CULL_FACE As Integer = 2884
    Private Const GL_CLAMP_TO_EDGE As Integer = 33071
    Private Const GL_TEXTURE_WRAP_S As Integer = 10242
    Private Const GL_TEXTURE_WRAP_T As Integer = 10243
    'Public Const GL_POINTS As Integer = &H0
    'Public Const GL_LINES As Integer = &H1
    'Public Const GL_LINE_LOOP As Integer = &H2
    'Public Const GL_LINE_STRIP As Integer = &H3
    'Public Const GL_TRIANGLE_STRIP As Integer = &H5
    'Public Const GL_TRIANGLE_FAN As Integer = &H6
    'Public Const GL_QUAD_STRIP As Integer = &H8
    'Public Const GL_POLYGON As Integer = &H9

    ' API methods

    Private Delegate Function WndProcDelgate(ByVal hWnd As IntPtr, ByVal Message As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    Private Declare Function glGetString Lib "opengl32" (ByVal name As Int32) As String
    Private Declare Sub glHint Lib "opengl32" (ByVal target As Int32, ByVal mode As Int32)
    Private Declare Sub glShadeModel Lib "opengl32" (ByVal mode As Int32)
    Private Declare Sub glFrustum Lib "opengl32" (ByVal left As Double, ByVal right As Double, ByVal bottom As Double, ByVal top As Double, ByVal near_val As Double, ByVal far_val As Double)
    Private Declare Auto Function RegisterClassEx Lib "user32" (ByRef pcWndClassEx As WNDCLASSEX) As Integer
    Private Declare Auto Function CreateWindowEx Lib "user32" (ByVal dwExStyle As Integer, ByVal lpClassName As String, ByVal lpWindowName As String, ByVal dwStyle As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hWndParent As IntPtr, ByVal hMenu As IntPtr, ByVal hInstance As IntPtr, ByVal lpParam As IntPtr) As IntPtr
    Private Declare Function ShowWindow Lib "user32" (ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As Boolean
    Private Declare Function UpdateWindow Lib "user32" (ByVal hWnd As IntPtr) As Boolean
    Private Declare Auto Function PeekMessage Lib "user32" (ByRef lpMsg As MSG, ByVal hWnd As IntPtr, ByVal wMsgFilterMin As Integer, ByVal wMsgFilterMax As Integer, ByVal wRemoveMsg As Integer) As Boolean
    Private Declare Function TranslateMessage Lib "user32" (ByRef lpMsg As MSG) As Boolean
    Private Declare Auto Function DispatchMessage Lib "user32" (ByRef lpMsg As MSG) As IntPtr
    Private Declare Sub PostQuitMessage Lib "user32" (ByVal nExitCode As Integer)
    Private Declare Function SetFocus Lib "user32" (ByVal hWnd As IntPtr) As IntPtr
    Private Declare Auto Function DefWindowProc Lib "user32" (ByVal hWnd As IntPtr, ByVal wMsg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    Private Declare Function GetDC Lib "user32" (ByVal hWnd As Int32) As Int32
    Private Declare Function ChoosePixelFormat Lib "gdi32" (ByVal deviceContext As IntPtr, ByRef pixelFormatDescriptor As PIXELFORMATDESCRIPTOR) As Int32
    Private Declare Function SetPixelFormat Lib "gdi32" (ByVal deviceContext As IntPtr, ByVal pixelFormat As Integer, ByRef pixelFormatDescriptor As PIXELFORMATDESCRIPTOR) As Boolean
    Private Declare Function wglCreateContext Lib "opengl32" (ByVal hDC As Int32) As Int32
    Private Declare Function wglMakeCurrent Lib "opengl32" (ByVal hDC As Int32, ByVal hglrc As Int32) As Boolean
    Private Declare Sub SwapBuffers Lib "gdi32" (ByVal dw1 As Int32)
    Private Declare Sub glBegin Lib "opengl32" (ByVal mode As Int32)
    Private Declare Sub glEnd Lib "opengl32" ()
    Private Declare Sub glVertex3f Lib "opengl32" (ByVal x As Single, ByVal y As Single, ByVal z As Single)
    Private Declare Sub glTranslatef Lib "opengl32" (ByVal x As Single, ByVal y As Single, ByVal z As Single)
    Private Declare Sub glClearDepth Lib "opengl32" (ByVal depth As Double) 'should be a single ?
    Private Declare Sub glDepthFunc Lib "opengl32" (ByVal func As Int32)
    Private Declare Sub glClearColor Lib "opengl32" (ByVal red As Single, ByVal green As Single, ByVal blue As Single, ByVal alpha As Single)
    Private Declare Sub glClear Lib "opengl32" (ByVal mask As Long)
    Private Declare Sub glEnable Lib "opengl32" (ByVal cap As Int32)
    Private Declare Sub glDisable Lib "opengl32" (ByVal cap As Int32)
    Private Declare Sub glMatrixMode Lib "opengl32" (ByVal mode As Int32)
    Private Declare Sub glViewport Lib "opengl32" (ByVal x As Int32, ByVal y As Int32, ByVal width As Int32, ByVal height As Int32)
    Private Declare Sub glLoadIdentity Lib "opengl32" ()
    Private Declare Sub glRotatef Lib "opengl32" (ByVal angle As Single, ByVal x As Single, ByVal y As Single, ByVal z As Single)
    Private Declare Sub glBindTexture Lib "opengl32" (ByVal target As Int32, ByVal texture As Int32)
    Private Declare Sub glGenTextures Lib "opengl32" (ByVal n As Int32, ByRef textures As Int32)
    Private Declare Sub glTexCoord2f Lib "opengl32" (ByVal s As Single, ByVal t As Single)
    Private Declare Sub glTexParameteri Lib "opengl32" (ByVal target As Int32, ByVal pname As Int32, ByVal param As Int32)
    Private Declare Sub glTexParameterf Lib "opengl32" (ByVal target As Int32, ByVal pname As Int32, ByVal param As Single)
    Private Declare Sub glTexImage2D Lib "opengl32" (ByVal target As Int32, ByVal level As Int32, ByVal internalFormat As Int32, ByVal width As Int32, ByVal height As Int32, ByVal border As Int32, ByVal format As Int32, ByVal t As Int32, ByVal pixels As IntPtr)

    ' Instance variables

    Private dc As Int32
    Private PixelFormat As PIXELFORMATDESCRIPTOR
    Private active As Boolean = True
    Private spin As Single = 0
    Private Started As Date = Now
    Private LastKeyChecked As Date = Now
    Private texture(0) As Int32
    Private texturecount As Integer = 0
    Private KeyState(500) As Boolean

    Private CameraRotateY As Single = 0
    Private CameraPosition As New Point3D(0, -4, 0)

    Private hm As New HeightMap(10, 10, 5)

    ' API structures

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Private Structure WNDCLASSEX
        Public cbSize As Integer ' Size in bytes of the WNDCLASSEX structure
        Public style As Integer ' Class style
        Public lpfnWndProc As WndProcDelgate ' Pointer to the classes Window Procedure
        Public cbClsExtra As Integer ' Number of extra bytes to allocate for class
        Public cbWndExtra As Integer ' Number of extra bytes to allocate for window
        Public hInstance As IntPtr ' Applications instance handle Class
        Public hIcon As IntPtr ' Handle to the classes icon
        Public hCursor As IntPtr ' Handle to the classes cursor
        Public hbrBackground As IntPtr ' Handle to the classes background brush
        Public lpszMenuName As String ' Resource name of class menu
        Public lpszClassName As String ' Name of the Window Class
        Public hIconSm As IntPtr ' Handle to the classes small icon
    End Structure

    <StructLayout(LayoutKind.Sequential)> _
    Private Structure POINTAPI
        Public x As Integer ' X-Coordinate in pixels
        Public y As Integer ' Y-Coordinate in pixels
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)> _
    Private Structure MSG
        Public hWnd As IntPtr ' Window handle of the associated window
        Public Message As Integer ' Message identifier
        Public wParam As IntPtr ' Additional message info
        Public lParam As IntPtr ' Additional message info
        Public time As Integer ' Time message was posted
        Public pt As POINTAPI ' Cursor position when message was posted
    End Structure

    Private Structure PIXELFORMATDESCRIPTOR
        Public nSize As UInt16 'word = 16 bits
        Public nVersion As UInt16 'word = 16 bits
        Public dwFlags As UInt32 ' dword = 32 bits
        Public iPixelType As Byte
        Public cColorBits As Byte
        Public cRedBits As Byte
        Public cRedShift As Byte
        Public cGreenBits As Byte
        Public cGreenShift As Byte
        Public cBlueBits As Byte
        Public cBlueShift As Byte
        Public cAlphaBits As Byte
        Public cAlphaShift As Byte
        Public cAccumBits As Byte
        Public cAccumRedBits As Byte
        Public cAccumGreenBits As Byte
        Public cAccumBlueBits As Byte
        Public cAccumAlphaBits As Byte
        Public cDepthBits As Byte
        Public cStencilBits As Byte
        Public cAuxBuffers As Byte
        Public iLayerType As Byte
        Public bReserved As Byte
        Public dwLayerMask As UInt32 ' dword = 32 bits
        Public dwVisibleMask As UInt32 ' dword = 32 bits
        Public dwDamageMask As UInt32 ' dword = 32 bits
    End Structure

    Public Sub New(ByVal Width As Integer, ByVal Height As Integer)

        Dim wndClass As New WNDCLASSEX
        Dim hWnd As IntPtr


        Dim hInstance As IntPtr = Marshal.GetHINSTANCE(GetType(OpenGLViewController).Module)

        With wndClass
            .cbSize = Marshal.SizeOf(wndClass)
            .style = CS_HREDRAW Or CS_VREDRAW
            .lpfnWndProc = New WndProcDelgate(AddressOf WindowMessageHandler)
            .cbClsExtra = 0
            .cbWndExtra = 0
            .hInstance = hInstance
            .hIcon = IntPtr.Zero
            .hIconSm = IntPtr.Zero
            .hCursor = IntPtr.Zero
            .hbrBackground = IntPtr.Zero
            .lpszMenuName = Nothing
            .lpszClassName = CLASS_NAME
        End With

        'this next line will intentionally fail because we don't have a valid rendering context
        'but call is a hack to "prioritise" opengl32 over gdi32 by making an opengl call first
        'if you are using a gl debugger such as glintercept then you will get a notice about this
        glGetString(GL_VERSION)

        ' register window class
        RegisterClassEx(wndClass)

        ' Create window
        hWnd = CreateWindowEx(WS_EX_APPWINDOW, CLASS_NAME, APP_TITLE, WS_POPUP, CType(Screen.PrimaryScreen.Bounds.Width / 2 - Width / 2, Integer), CType(Screen.PrimaryScreen.Bounds.Height / 2 - Height / 2, Integer), Width, Height, IntPtr.Zero, IntPtr.Zero, hInstance, Nothing)

        dc = GetDC(CType(hWnd, Integer))
        PixelFormat = CreatePixelFormatDescriptor()
        Dim PixelFormatIndex As Integer = ChoosePixelFormat(CType(dc, System.IntPtr), PixelFormat)
        Dim Success As Boolean = SetPixelFormat(CType(dc, System.IntPtr), PixelFormatIndex, PixelFormat)

        Dim hrc As Int32 = wglCreateContext(dc)

        Success = wglMakeCurrent(dc, hrc)

        ShowWindow(hWnd, SW_SHOWNORMAL)
        UpdateWindow(hWnd)
        SetFocus(hWnd)

        ResizeGLScene(Width, Height)

        InitGL()
        LoadTextures()

        hm.Randomize()

        RenderLoop()

    End Sub

    Private Sub RenderLoop()

        Dim message As MSG

        Do While active = True

            PeekMessage(message, IntPtr.Zero, 0, 0, PM_REMOVE)

            ProcessKeys()

            DrawScene()
            TranslateMessage(Message)
            DispatchMessage(Message)

        Loop

    End Sub

    Private Sub ProcessKeys()

        Dim ElapsedMs As Double = Now.Subtract(LastKeyChecked).TotalMilliseconds
        LastKeyChecked = Now

        Dim RotateSpeed As Single = 45 ' degrees per second

        'left
        If KeyState(37) = True Then
            CameraRotateY -= RotateSpeed * CType(ElapsedMs / 1000, Single)
            If CameraRotateY < 0 Then
                CameraRotateY = 360
            End If
        End If

        'right
        If KeyState(39) = True Then
            CameraRotateY += RotateSpeed * CType(ElapsedMs / 1000, Single)
            If CameraRotateY > 360 Then
                CameraRotateY = 0
            End If
        End If

        Dim MoveSpeed As Single = 15 ' scene units per second

        'up
        If KeyState(38) = True Then
            CameraPosition.x -= CType(Math.Sin(CameraRotateY / 360 * 2 * Math.PI) * MoveSpeed * CType(ElapsedMs / 1000, Single), Single)
            CameraPosition.z += CType(Math.Cos(CameraRotateY / 360 * 2 * Math.PI) * MoveSpeed * CType(ElapsedMs / 1000, Single), Single)
        End If

        'down
        If KeyState(40) = True Then
            CameraPosition.x += CType(Math.Sin(CameraRotateY / 360 * 2 * Math.PI) * MoveSpeed * CType(ElapsedMs / 1000, Single), Single)
            CameraPosition.z -= CType(Math.Cos(CameraRotateY / 360 * 2 * Math.PI) * MoveSpeed * CType(ElapsedMs / 1000, Single), Single)
        End If

    End Sub

    Private Sub InitGL()

        glShadeModel(GL_SMOOTH)
        glClearDepth(1.0F) ' Depth Buffer Setup
        glEnable(GL_DEPTH_TEST) ' Enables Depth Testing
        glDepthFunc(GL_LEQUAL) ' The Type Of Depth Test To Do
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST) ' Really Nice Perspective Calculations
        glClearColor(0.2F, 0.2F, 0.3F, 0.0F)
        glEnable(GL_CULL_FACE)

    End Sub

    Private Sub LoadTextures()

        LoadTexture("texture.bmp")
        LoadTexture("sand.bmp")
        LoadSkybox("sunset_panoramic.jpg")

    End Sub


    Private Function LoadTexture(ByVal FileName As String) As Integer

        If System.IO.File.Exists(FileName) = False Then Exit Function 'safeguard against missing image

        texturecount += 1
        ReDim Preserve texture(texturecount - 1)

        Dim Image As Bitmap = New Bitmap(FileName)
        Image.RotateFlip(RotateFlipType.RotateNoneFlipY)

        Dim bitmapdata As System.Drawing.Imaging.BitmapData
        Dim rect As Rectangle = New Rectangle(0, 0, Image.Width, Image.Height)

        bitmapdata = Image.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, Drawing.Imaging.PixelFormat.Format24bppRgb)

        glGenTextures(1, texture(texturecount - 1))
        glBindTexture(GL_TEXTURE_2D, texture(texturecount - 1))
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image.Width, Image.Height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, bitmapdata.Scan0)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)   ' Linear Filtering
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)   ' Linear Filtering

        
        Image.UnlockBits(bitmapdata)
        Image.Dispose()

        Return texturecount - 1

    End Function

    Private Sub LoadSkybox(ByVal FileName As String)

        If System.IO.File.Exists(FileName) = False Then Exit Sub 'safeguard against missing image

        For PanelIndex As Integer = 0 To 3

            texturecount += 1
            ReDim Preserve texture(texturecount - 1)

            Dim Image As System.Drawing.Image = Bitmap.FromFile(FileName)
            
            ' power of 2 size bitmap in memory
            Dim TBitmap As New Bitmap(512, 512, Imaging.PixelFormat.Format24bppRgb)
            Dim g As Graphics = Graphics.FromImage(TBitmap)
            g.Clear(Color.Aqua)

            Dim PanelWidth As Integer = CType(Image.Width / 4, Integer)
            g.DrawImage(Image, New Rectangle(0, 0, TBitmap.Width, CType(TBitmap.Height * 0.5, Integer)), New Rectangle(PanelWidth * PanelIndex, CType(Image.Height * 0.5, Integer), PanelWidth, CType(Image.Height * 0.5, Integer)), GraphicsUnit.Pixel)
            g.Dispose()

            TBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY)

            Dim bitmapdata As System.Drawing.Imaging.BitmapData
            Dim rect As Rectangle = New Rectangle(0, 0, TBitmap.Width, TBitmap.Height)

            bitmapdata = TBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, Drawing.Imaging.PixelFormat.Format24bppRgb)


            glGenTextures(1, texture(texturecount - 1))
            glBindTexture(GL_TEXTURE_2D, texture(texturecount - 1))
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, TBitmap.Width, TBitmap.Height, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, bitmapdata.Scan0)


            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)   ' Linear Filtering
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)   ' Linear Filtering


            TBitmap.UnlockBits(bitmapdata)
            TBitmap.Dispose()
            Image.Dispose()

        Next

    End Sub


    Private Sub ResizeGLScene(ByVal Width As Integer, ByVal Height As Integer)
        glViewport(0, 0, Width, Height)
        glMatrixMode(GL_PROJECTION)                     ' Select The Projection Matrix
        glLoadIdentity()                                ' Reset The Projection Matrix

        Dim Aspect As Double = CType(Width, Double) / CType(Height, Double)
        ReplacementGluPerspective(45.0F, Aspect, 0.1F, 100.0F)

        glMatrixMode(GL_MODELVIEW)                      ' Select The Modelview Matrix
        glLoadIdentity()                                ' Reset The Modelview Matrix

    End Sub

    Private Sub ReplacementGluPerspective(ByVal fovy As Double, ByVal aspect As Double, ByVal zNear As Double, ByVal zFar As Double)

        Dim xmin, xmax, ymin, ymax As Double

        ymax = zNear * Math.Tan(fovy * Math.PI / 360.0)
        ymin = -ymax
        xmin = ymin * aspect
        xmax = ymax * aspect

        glFrustum(xmin, xmax, ymin, ymax, zNear, zFar)

    End Sub

    Private Sub DrawScene()

        glEnable(GL_DEPTH_TEST)

        ' set camera position 
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glFrustum(-0.05F, 0.05F, -0.04F, 0.04F, 0.1F, 100.0F)
        glRotatef(CameraRotateY, 0, 1, 0)
        glTranslatef(CameraPosition.x, CameraPosition.y, CameraPosition.z)

        ' switch mode to model
        glMatrixMode(GL_MODELVIEW)

        'Reset the back buffer
        glClear(GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT)
        glLoadIdentity()

        glTranslatef(0.0F, 4.0F, -6.0F)

        spin = CType(Now.Subtract(Started).TotalMilliseconds, Single) / 1000 * 0.5F ' half a rotation per second
        spin -= CType(Math.Floor(spin), Single)
        spin *= 360

        glRotatef(spin, 0, 1, 0)

        'glDisable(GL_TEXTURE_2D)
        glEnable(GL_TEXTURE_2D)                     ' enable use of textures

        glBindTexture(GL_TEXTURE_2D, texture(0))    ' use texture 0

        glDisable(GL_CULL_FACE)                     ' draw triangle and quad double sided

        glBegin(GL_TRIANGLES)                       ' Draw triangle
        glTexCoord2f(0.5F, 1.0F)
        glVertex3f(0.0F, 1.0F, 0.0F)                ' Top
        glTexCoord2f(0.0F, 0.0F)
        glVertex3f(-1.0F, -1.0F, 0.0F)              ' Bottom left
        glTexCoord2f(1.0F, 0.0F)
        glVertex3f(1.0F, -1.0F, 0.0F)               ' Bottom right
        glEnd()                                     ' Finished triangle


        glBegin(GL_QUADS)
        glTexCoord2f(1.0F, 1.0F)                    ' top right of texture
        glVertex3f(1.0F, 1.0F, 1.0F)                ' top right of quad
        glTexCoord2f(0.0F, 1.0F)                    ' top left of texture
        glVertex3f(-1.0F, 1.0F, 1.0F)               ' top left of quad
        glTexCoord2f(0.0F, 0.0F)                    ' bottom left of texture
        glVertex3f(-1.0F, -1.0F, 1.0F)              ' bottom left of quad
        glTexCoord2f(1.0F, 0.0F)                    ' bottom right of texture
        glVertex3f(1.0F, -1.0F, 1.0F)               ' bottom right of quad
        glEnd()


        glLoadIdentity()
        'glTranslatef(CType(-hm.MaxCol * hm.TileSize * 0.5, Single), -CType(hm.MaxHeight, Single) * 2, CType(-hm.MaxRow * hm.TileSize * 0.5, Single))
        glTranslatef(CType(-hm.MaxCol * hm.TileSize * 0.5, Single), 0, CType(-hm.MaxRow * hm.TileSize * 0.5, Single))

        'glEnable(GL_TEXTURE_2D)
        glEnable(GL_CULL_FACE) ' backface culling on heightmap quads

        glBindTexture(GL_TEXTURE_2D, texture(1))    ' use texture 1

        'draw heightmap
        For r As Integer = 0 To hm.MaxRow - 1
            For c As Integer = 0 To hm.MaxCol - 1

                Dim YTopLeft As Single = CType(Math.Sin(spin / 360 * Math.PI * 2), Single) * hm.Height(r, c)
                Dim YTopRight As Single = CType(Math.Sin(spin / 360 * Math.PI * 2), Single) * hm.Height(r + 1, c)
                Dim YBottomLeft As Single = CType(Math.Sin(spin / 360 * Math.PI * 2), Single) * hm.Height(r, c + 1)
                Dim YBottomRight As Single = CType(Math.Sin(spin / 360 * Math.PI * 2), Single) * hm.Height(r + 1, c + 1)

                Dim X1 As Single = r * hm.TileSize
                Dim Z1 As Single = c * hm.TileSize
                Dim X2 As Single = (r + 1) * hm.TileSize
                Dim Z2 As Single = (c + 1) * hm.TileSize

                glBegin(GL_QUADS)
                glTexCoord2f(1.0F, 1.0F)                    ' top right of texture
                glVertex3f(X2, YTopRight, Z1)               ' top right of quad
                glTexCoord2f(0.0F, 1.0F)                    ' top left of texture
                glVertex3f(X1, YTopLeft, Z1)                ' top left of quad
                glTexCoord2f(0.0F, 0.0F)                    ' bottom left of texture
                glVertex3f(X1, YBottomLeft, Z2)             ' bottom left of quad
                glTexCoord2f(1.0F, 0.0F)                    ' bottom right of texture
                glVertex3f(X2, YBottomRight, Z2)            ' bottom right of quad
                glEnd()


            Next
        Next


        ' draw skybox
        glLoadIdentity()
        'Dim GL_FLAT As Integer = 7424
        'glShadeModel(GL_FLAT)

        'glDisable(GL_DEPTH_TEST) ' // make sure the skybox doesn't get culled
        'Dim GL_LIGHTING As Integer = 2896
        'glDisable(GL_LIGHTING)

        Dim SkyBoxSize As Single = CType((hm.MaxRow) * hm.TileSize * 0.5, Single)

        'glTranslatef(0, SkyBoxSize, 0)

        'front
        glBindTexture(GL_TEXTURE_2D, texture(2))


        glBegin(GL_QUADS)
        glTexCoord2f(1.0F, 1.0F)
        glVertex3f(SkyBoxSize, SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(0.0F, 1.0F)
        glVertex3f(-SkyBoxSize, SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(0.0F, 0.0F)
        glVertex3f(-SkyBoxSize, -SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(1.0F, 0.0F)
        glVertex3f(SkyBoxSize, -SkyBoxSize, -SkyBoxSize)
        glEnd()

        'behind
        glBindTexture(GL_TEXTURE_2D, texture(4))
        glBegin(GL_QUADS)
        glTexCoord2f(0.0F, 0.0F)
        glVertex3f(SkyBoxSize, -SkyBoxSize, SkyBoxSize)
        glTexCoord2f(1.0F, 0.0F)
        glVertex3f(-SkyBoxSize, -SkyBoxSize, SkyBoxSize)
        glTexCoord2f(1.0F, 1.0F)
        glVertex3f(-SkyBoxSize, SkyBoxSize, SkyBoxSize)
        glTexCoord2f(0.0F, 1.0F)
        glVertex3f(SkyBoxSize, SkyBoxSize, SkyBoxSize)
        glEnd()

        'right
        glBindTexture(GL_TEXTURE_2D, texture(3))

        glBegin(GL_QUADS)
        glTexCoord2f(1.0F, 1.0F)
        glVertex3f(SkyBoxSize, SkyBoxSize, SkyBoxSize)
        glTexCoord2f(0.0F, 1.0F)
        glVertex3f(SkyBoxSize, SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(0.0F, 0.0F)
        glVertex3f(SkyBoxSize, -SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(1.0F, 0.0F)
        glVertex3f(SkyBoxSize, -SkyBoxSize, SkyBoxSize)
        glEnd()

        'left
        glBindTexture(GL_TEXTURE_2D, texture(5))
        glBegin(GL_QUADS)
        glTexCoord2f(0.0F, 0.0F)
        glVertex3f(-SkyBoxSize, -SkyBoxSize, SkyBoxSize)
        glTexCoord2f(1.0F, 0.0F)
        glVertex3f(-SkyBoxSize, -SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(1.0F, 1.0F)
        glVertex3f(-SkyBoxSize, SkyBoxSize, -SkyBoxSize)
        glTexCoord2f(0.0F, 1.0F)
        glVertex3f(-SkyBoxSize, SkyBoxSize, SkyBoxSize)
        glEnd()



        'Swap the back buffer with the front buffer
        SwapBuffers(dc)

    End Sub

    Private Function WindowMessageHandler(ByVal hWnd As IntPtr, ByVal Message As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr

        Select Case Message
            Case WM_KEYDOWN
                KeyState(CType(wParam, Integer)) = True
                Return IntPtr.Zero
            Case WM_KEYUP
                KeyState(CType(wParam, Integer)) = False
                Return IntPtr.Zero
            Case WM_LBUTTONDOWN
                active = False
                Return IntPtr.Zero
            Case WM_RBUTTONDOWN
                Return IntPtr.Zero
            Case WM_DESTROY
                PostQuitMessage(0)
                Return IntPtr.Zero
            Case Else
                Return DefWindowProc(hWnd, Message, wParam, lParam)
        End Select

    End Function

    Private Function CreatePixelFormatDescriptor() As PIXELFORMATDESCRIPTOR
        Dim NewPixelFormat As New PIXELFORMATDESCRIPTOR
        'set only what we want and leave the rest as 0
        'size of struct 
        NewPixelFormat.nSize = CType(System.Runtime.InteropServices.Marshal.SizeOf(NewPixelFormat), UShort)
        'version
        NewPixelFormat.nVersion = 1
        'gdi flags
        NewPixelFormat.dwFlags = PFD_DRAW_TO_WINDOW Or PFD_SUPPORT_OPENGL Or PFD_DOUBLEBUFFER
        'colour depth use 16bit
        NewPixelFormat.iPixelType = PFD_TYPE_RGBA
        'depth buffer 16bit
        NewPixelFormat.cDepthBits = CType(16, Byte)
        'no stencil buffer
        NewPixelFormat.cStencilBits = 0
        'main drawing layer
        NewPixelFormat.iLayerType = PFD_MAIN_PLANE
        Return NewPixelFormat
    End Function

    Public Shared Sub Main()
        Dim wnd As New OpenGLViewController(640, 480)
    End Sub



    Private Class HeightMap

        Private mHeights(,) As Single
        Private mRows As Integer
        Private mCols As Integer

        Public MaxHeight As Single = 2
        Public TileSize As Single = 10

        Sub New(ByVal Rows As Integer, ByVal Cols As Integer, ByVal MapTileSize As Single)
            TileSize = MapTileSize
            mRows = Rows
            mCols = Cols
            ReDim mHeights(Rows, Cols)
        End Sub

        Public ReadOnly Property MaxRow() As Integer
            Get
                Return mRows - 1
            End Get
        End Property

        Public ReadOnly Property MaxCol() As Integer
            Get
                Return mCols - 1
            End Get
        End Property
        Public Property Height(ByVal Row As Integer, ByVal Col As Integer) As Single
            Get
                If Row >= 0 And Row <= mRows - 1 Then
                    If Col >= 0 And Col <= mCols - 1 Then
                        Return mHeights(Row, Col)
                    End If
                End If
                Return 0
            End Get
            Set(ByVal value As Single)
                If Row >= 0 And Row <= mRows - 1 Then
                    If Col >= 0 And Col <= mCols - 1 Then
                        mHeights(Row, Col) = value
                    End If
                End If
            End Set
        End Property

        Public Sub Randomize()
            Dim Rnd As New Random(0) 'fixed seed for consistent results

            For r As Integer = 0 To mRows - 1

                For c As Integer = 0 To mCols - 1
                    Height(r, c) = CType(Rnd.NextDouble, Single) * MaxHeight
                Next

            Next

            'flatten edges
            For c = 0 To mCols - 1
                Height(0, c) = 0
                Height(mRows - 1, c) = 0
            Next

            For r = 0 To mRows - 1
                Height(r, 0) = 0
                Height(r, mCols - 1) = 0
            Next

        End Sub

    End Class

    Private Class Point3D
        Public x As Single
        Public y As Single
        Public z As Single
        Sub New()
            Me.New(0, 0, 0)
        End Sub
        Sub New(ByVal nx As Single, ByVal ny As Single, ByVal nz As Single)
            x = nx
            y = ny
            z = nz
        End Sub
    End Class

End Class
